%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%  Base de Datos %%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%

% Calderon Sanchez, Alejandro
% Diaz Sanchez, Ruben
% Villegas Nunez, Carlos

%Indica que la definición del predicado "conocido" puede variar durante la ejecución de aquí en adelante.
:- dynamic(marco/2).

%El marco conocido almacena el historial de las consultas realizadas anteriormente.
marco(atributo,[
	valor-[def(desconocido)],
	conocido-[def(desconocido)]
	]).

%Predicado objetivo.
objetivo(Artropodo) :- ('artropodo', ('nombre', Artropodo)).

%Bucle principal del sistema experto, nos permitirá en cada iteración realizar una acción, hasta que introduzcamos el carácter 'e', cumpliéndose el predicado X==e.
comenzar :- bienvenida,
    repeat,
    write('> '), read(X),
    ejecutar(X),
    X == e.

%Muesta el menú de ejecución del sistema experto, indicando las diversas opción que podemos realizar. 
bienvenida :-
    write('Bienvenido al Sistema Experto'),
    nl, write('Teclee: '),
    write('l (carga), '),
    write('c (consulta), '),
    write('h (ayuda), '),
    write('r (recuerda), '),
    write('e (finaliza)'), nl.

%Conecta con la base de datos.
ejecutar(l) :- write('Conectando con la base de datos...'),
    odbc_connect('base_conocimiento', handler, [user(rualca), password(calcetin), alias(handler), open(once)]), ['src/marcos-Shell.pl'], write('Ok'), nl, !.

%Ejecuta el predicado "resolver" que se encargará de realizar la consulta que desee el usuario.
ejecutar(c) :- resolver, !.

%Ejecuta el predicado de ayuda.
ejecutar(h) :-
    write('l(load), c(consultar), h(ayuda), r(recuerda), e(finaliza)'), nl, !.

%Muestra el historial de los predicados introducidos, listándolos.
ejecutar(r) :- listing(conocido), !.

%Finaliza la ejecución del sistema experto.
ejecutar(e) :- !.

%Rama de error del sistema experto la cual se ejecuta si te ha introducido otra opción a la permitidas.
ejecutar(X) :- write(X), write(' inválido'), nl, fail.

%Es el predicado encargado del control de la consulta. Primero resetea el valor del predicado "conocido" a su valor por defecto, comprobando que así ha sido con "asserta". Luego comienza la búsqueda, a través de "candidatos", para cumplir el objetivo.
resolver :- 
    abolish(marco, 2),
    asserta(marco(sin_valor,sin_valor)),
    candidatos('artropodo', 'nombre', X),
    write('Respuesta: '), write(X), nl.

resolver :- write('No se encontró respuesta'), nl.

%Historizar recorre la lista de predicados de conocidos en busca de alguno que cumpla el objetivo.
historizar([Objtv|Resto], Hist) :- !, prueba(Objtv, [Objtv|Hist]), historizar(Resto, Hist).
historizar(Objtv, Hist) :- prueba(Objtv, [Objtv|Hist]).

%"candidatos" busca todos los candidatos posibles del objetivo en la base de datos. En nuestro caso será (nombre,abeja),(nombre,escorpión),etc...
%con "comprobar_candidatos" vamos uno a uno intentando encontrar el que estamos buscando.
comprobar_candidatos([], false).
comprobar_candidatos([(A, V)|_], V) :- historizar((A,V), Hist), !.
comprobar_candidatos([(A, V)|Resto], X) :- comprobar_candidatos(Resto, X).	

seleccionar_subobjetivos(OBJ, SUB) :-
    concat_atom(['SELECT atributo, valor FROM artropodos where elemento = "', OBJ,'"'],'',SQL), findall(X , odbc_query(handler, SQL, X, [types([atom, atom]),null(nullski)]) , Q), consulta(Q, SUB), longitud(SUB, N), N > 0.

candidatos(OBJ, _, X) :- 
    seleccionar_subobjetivos(OBJ, SUB), comprobar_candidatos(SUB, X).

%Prueba recorre las distintas características del predicado conocido, preguntando una a una si coinciden con el objetivo propuesto.
%Fin del recorrido.
prueba(true, _) :- !.

%En este "prueba" comprobamos si es un nodo hoja del árbol, en caso de que no lo sea se llama a historizar con sus hijos.
prueba((A, V), Hist) :-
    seleccionar_subobjetivos(V, Subjtvs), historizar(Subjtvs, Hist).

%En caso de que sea un nodo hoja se llama a "coinciden" para comprobar si ya es conocido o si se le pregunta al usuario
prueba((A, V), Hist) :-
    coinciden((A, V), Hist), !.

%Funciones auxiliares para las consultas, para ver la cantidad de resultados, y para formatear la salida de forma que podamos trabajar con ella
consulta([],[]).
consulta([row(A,V)],[(A, V)]).
consulta([row(A,V)|R],RES) :- consulta(R,S2), concatenar([(A, V)],S2,RES).

longitud([], 0).
longitud([H|T], N) :- longitud(T, N0), N is N0 + 1.

concatenar([],L,L).
concatenar([X|L1],L2,[X|L3]) :- concatenar(L1,L2,L3).

%"comphijos" comprueba si los hijos son conocidos, para marcar como conocida una propiedad
comphijos([(A,V)]) :- obtener_marco(A,[valor-V,conocido-sí]).
comphijos([(A,V)|R]) :- obtener_marco(A,[valor-V,conocido-sí]), comphijos(R).

compnohijos(Subjtvs) :- not(comphijos(Subjtvs)). 

%Mira si el par (A,V) ya está registrado como sí
coinciden((Atributo, Valor), _) :-
    obtener_marco(Atributo,[valor-Valor,conocido-sí]), !.

%Mira si el par (A,V) ya está registrado como no (o cualquier otra cosa).
coinciden((Atributo, Valor), _) :-
    obtener_marco(Atributo,[valor-Valor,conocido-no]), !, fail.

%Comprobamos si se ha preguntado por ese Atributo
coinciden((Atributo, Valor), Hist) :-
    obtener_marco(Atributo,[valor-X,conocido-sí]), X \== Valor,
    agregar_marco(Atributo, [valor-Valor,conocido-no]), !, fail.

%Primero miramos si no es nodo hoja, y en ese caso miramos si sus hijos son conocidos. En caso de que lo sean, daremos la propiedad por conocida.
coinciden((A, V), Hist) :-
    seleccionar_subobjetivos(V, Subjtvs),
    comphijos(Subjtvs),
    write('Es '), write(A = V), nl, agregar_marco(A, [valor-V,conocido-sí]), !.

%Si no miramos si alguno de sus hijos es conocido pero con valor NO. En cuyo caso daremos la propiedad por negativa.
coinciden((A, V), Hist) :-
    seleccionar_subobjetivos(V, Subjtvs),
    compnohijos(Subjtvs),
    %write('No es '), write(A = V), nl,
    agregar_marco(A, [valor-V,conocido-no]), !, fail.

%No hay existencia conocida, por lo que se realiza la pregunta sobre la características, si el usuario contesta si, se introducido en la base de conocimiento.
coinciden((Atributo, Valor), Hist) :-
    write('¿Es '), write(Atributo = Valor), write('? '),
    leer_respuesta(Resp, Hist),
    agregar_marco(Atributo, [valor-Valor,conocido-Resp]), Resp == sí.

%Lee de la entrada estandar la respuesta del usuario.
leer_respuesta(X, Hist) :-
    repeat, read(X),
    procesar_respuesta(X, Hist), !.

procesar_respuesta(por_qué, Hist) :-
    write(Hist), escribir_historico(Hist),
    nl, !, fail.

procesar_respuesta(_, _).

escribir_historico(Hist) :-
    eliminar_primero(Hist, Hist0),
    eliminar_ultimo(Hist0, HistFinal),
    write(HistFinal).

eliminar_primero([_|R],R).

eliminar_ultimo(L, R) :-
    reverse(L, L0),
    eliminar_primero(L0, L1),
    reverse(L1, R).

